package thread;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: wx
 * Date: 2024-02-14
 * Time: 22:41
 *
 * CountDownLatch 针对特定场景解决问题的小工具
 *  多线程执行一个任务,这个任务把大的任务拆成几个部分,有几个线程分别执行
 *  如果使用 jion 方式,就只能使用每个线程执行一个任务
 *  借助 countDownLatch 就可以让一个线程能执行多个任务
 */
public class ThreadDemo37 {
    public static void main(String[] args) throws InterruptedException {

        //1.此处构造方法中写 10,意思是 有 10 个线程/任务
        CountDownLatch latch = new CountDownLatch(10);

        //创建出十个线程去下载.
        for (int i = 0; i < 10; i++) {
            int id = i;
            Thread t = new Thread(() -> {
                Random random = new Random();
                int  time = (random.nextInt(5) + 1) * 1000;//[0,5)
                System.out.println("线程" + id + "开始下载");
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程" + id + "结束下载");
                //2.告知 CountDownLatch 我执行结束了
                latch.countDown();
            });
            t.start();
        }
        //3.通过这个await 操作来等待所有任务结束,也就是 countDown被调用 10 次了
        latch.await();
        System.out.println("所有任务都已经完成了!");
    }
}
/*
* vector Stack Hashtable 把关键方法加上了 synchronized
* 这几个,无论如何都得加锁,哪怕单线程也得加锁.不科学
* 这几个官方已经不建议使用
*
* 多线程环境
* 1.使用 ArrayList
*
* 2.Collection,synchronizedList(new ArrayList)
*             相当于给 ArrayList 套了个壳 通过套壳之后,得到了新的对象,新的对象里面的关键方法都是 带有锁的
*
* 3.使用 CopyOnWriteArrayList 写时拷贝 线程安全问题 多个线程修改同一个数据
*    这里有一个顺序表
* 如果多线程读这个顺序表, 没有任何线程安全问题
* 一旦有线程要修改 复制一份并且修改,让这个顺序表指向被修改的这里的顺序表
* 没有涉及加锁,也可以保证线程安全问题
* 把顺序表复制一份.修改新的顺序表内容 并且修改引用的指向
* 但是这种操作有很大的局限性
*   1)修改不能太频繁
*   2)顺序表不能太大
*      会导致开销太大
*      比如,服务器加载配置文件的时候,就需要把配置文件的内容解析出来放到内存中的数据结构中(顺序表\哈希表)
*      服务器配置文件,修改频率很低
*      配置文件一般体积不是很大,几kb 就不小了
*
* 4.多线程环境使用队列
* 1)自己加锁
* 2)BlockingQueue(线程安全的)
*
* 5.多线程环境使用 hash 表
* HashMap 肯定不行(线程不安全)
* HashTable 就在关键方法上添加了 synchronized (存在局限性)
* 后来标准库引入了更好的解决方法
* ConcurrentHashMap
*
*  ConcurrentHashMap 的改进:
* 1.缩小了锁的粒度
* hashtable 直接在方法上 使用 synchronized,就相当于是对 this 加锁
* 此时,尝试修改两个不同的元素,都会触发锁冲突
* ConcurrentHashMap 就是把锁变小了,给每个链表都发了一个锁
* 上述设定,不会产生更多的空间代价,因为java 中任何一个对象都可以直接作为锁对象
* 本身 hash表中,就得有数组,数组元素都是已经存在的(每个链表的头结点)
* 只要使用数组元素(链表头结点)作为加锁的对象即可.
* 上述也叫锁桶(hash表 也叫 hash桶)
* 在 java 1.7 及其之前.这种设定,不太合适.实现更复杂,效率也不够高,引入额外的空间开销
* 因此,在 java 1.8 开始,这里就设定成了每个链表一把锁
*
* 仔细观察就发现,如果修改两个不同链表上的元素.不涉及到线程安全问题,修改不同变量
* 如果修改是同一个链表上的元素,就可能涉及到线程安全问题
* 比如这两变量在链表上是相邻的  操作引入的时候,就可能涉及到操作同一个引用
* 此时,针对同一个链表操作,在加锁,针对不同链表操作,不必加锁了(不要产生锁冲突)
*
* 2.充分使用 CAS 原子操作,减少一些加锁
*  比如,针对 hash 表 元素个数维护
* synchronized 里头不是刚开始是偏亮级锁?
* synchronized 有可能成为重量级锁的.是否升级.并不可控
*
* 3.针对扩容操作的优化
* 扩容是一个重量操作
* 负载因子 描述了每个桶上平均多少个元素
*              此时桶上的元素不应该太长
*                如果太长
*                 1)变成树(长度不平均)
*                 2)扩容 -> 创建一个更大的数组,把旧的 hash表 的元素都给搬运到(删除\插入)新的数组上
*                           如果 hash表本省元素非常多,这里的扩容就会消耗很长的时间
*                     hash表平时很快 突然某个操作就慢了 过一会又快了(表现不稳定)
*                    无法控制何时扩容,一旦扩容触发了,就会导致操作非常耗时
*                    优化:化整为零,蚂蚁搬家
*                       hashmap的扩容操作是一把梭了 在某次插入元素操作中,整体完成扩容了
*                       ConcurrentHashMap 每次扩容只搬运一部分
*                                   假设这里一共有 1 kw 个元素
*                                   此时扩容的时候,每次插入/查抄/删除,都会搬运一部分
*                                就确保每次操作消耗的时间都不会很长 避免出现很卡的情况了
*                     扩容整体来说是一个比较低频的操作,运行整个程序运行一整天都不一定出发一次扩容(提前设置好容量)
*                      在扩容的过程中,同时存在两份 hash表
*                      一份是旧的,一份是新的
*                       插入操作,直接往新的上插入. 删除操作,新旧都直接删除
*                       查找删除,新的和旧的都得查询一下
* 0.75 是负载因子是默认的扩容阈值,不是负载因子本体
* 拿着实际的元素个数/数组的长度(桶的个数)算出来的数值
* 这个值可能是0.1,可能是0.5
* 拿着这个值 和 扩容阈值 进行比较,看看是否需要扩容
*
*/